﻿using IdentityModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Security.Claims;
using System.Threading.Tasks;
using System;
using Volo.Abp.Identity;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using Microsoft.AspNetCore.Identity;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
using IdentityUser = Volo.Abp.Identity.IdentityUser;

namespace Wuther.Infrastructure.ExtensionGrantTypes
{
    public class ExtensionValidate
    {
        readonly DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime();
        /// <summary>
        /// 服务器验证并输出用户信息，以便自动生成token
        /// </summary>
        /// <param name="loginProvider"></param>
        /// <param name="providerKey"></param>
        /// <returns></returns>
        public async Task<IActionResult> ServerValidate(ExtensionGrantContext context, string loginProvider, string providerKey)
        {
            var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();
            var user = await userManager.FindByLoginAsync(loginProvider, providerKey);
            if (user == null)
            {
                if(loginProvider == ExtensionGrantType.SMS)
                {
                    await SMSRegisterLogin(context, loginProvider, providerKey);
                }
                else if(loginProvider == ExtensionGrantType.WeChat)
                {
                    await WechatRegisterLogin(context, loginProvider, providerKey);
                }
                else if (loginProvider == ExtensionGrantType.Captcha)
                {
                    await CaptchaRegisterLogin(context, loginProvider, providerKey);
                }
                else
                {
                    throw new Exception("暂不支持该登录方式");
                }
                user = await userManager.FindByLoginAsync(loginProvider, providerKey);
            }

            // Create the claims-based identity that will be used by OpenIddict to generate tokens.
            var identity = new ClaimsIdentity(
                authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                nameType: Claims.Name,
                roleType: Claims.Role);

            // Add the claims that will be persisted in the tokens.
            identity.AddClaim(Claims.Subject, await userManager.GetUserIdAsync(user))
                    .AddClaim(Claims.Email, await userManager.GetEmailAsync(user))
                    .AddClaim(Claims.Name, await userManager.GetUserNameAsync(user));

            var roles = await userManager.GetRolesAsync(user);
            foreach (var role in roles)
            {
                identity.AddClaim(Claims.Role, role.ToString());
            }

            long authTime = (long)(DateTime.Now.ToLocalTime() - DateTime1970).TotalSeconds;
            identity.AddClaim(Claims.AuthenticationTime, authTime.ToString());

            foreach (Claim claim in identity.Claims)
            {
                claim.SetDestinations(GetDestinations(claim));
            }

            var principal = new ClaimsPrincipal(identity);
            // Note: in this sample, the granted scopes match the requested scope
            // but you may want to allow the user to uncheck specific scopes.
            // For that, simply restrict the list of scopes before calling SetScopes.
            var scopeStr = context.Request.GetParameter("scope").Value.ToString();
            if (!string.IsNullOrEmpty(scopeStr))
            {
                var scopes = scopeStr.Split(',').ToImmutableArray();
                principal.SetScopes(scopes);
                principal.SetResources(await GetResourcesAsync(context, scopes));
            }
            return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);
        }

        private async Task SMSRegisterLogin(ExtensionGrantContext context, string loginProvider, string providerKey)
        {
            var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();

            var user = (await userManager.GetUsersForClaimAsync(new Claim(JwtClaimTypes.PhoneNumber, providerKey))).FirstOrDefault();
            if (user == null)
            {
                user = new IdentityUser(Guid.NewGuid(), providerKey, $"{providerKey}@ilawyer.com", null);
                user.SetPhoneNumber(providerKey, true);
                var result = userManager.CreateAsync(user, "Pass123$").Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
                //添加短信登录角色
                await userManager.AddToRoleAsync(user, "sms");
                result = userManager.AddClaimsAsync(user, new Claim[]{
                    new Claim(JwtClaimTypes.Name,user.Name.IsNullOrEmpty()?user.UserName:user.Name),
                    new Claim(JwtClaimTypes.PhoneNumber, providerKey),
                    //new Claim(JwtClaimTypes.GivenName, user.UserName),
                    //new Claim(JwtClaimTypes.FamilyName, user.NormalizedUserName),
                    new Claim(JwtClaimTypes.Email, user.Email),
                    new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                    new Claim(JwtClaimTypes.WebSite, "https://valishment.cn"),
                }).Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
            }
            var userLogin = new UserLoginInfo(loginProvider, providerKey, "手机号登录");
            var loginRes = userManager.AddLoginAsync(user, userLogin).Result;
            if (!loginRes.Succeeded)
            {
                throw new Exception("登陆失败");
            }
        }
        private async Task WechatRegisterLogin(ExtensionGrantContext context, string loginProvider, string providerKey)
        {
            var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();

            var user = (await userManager.GetUsersForClaimAsync(new Claim("openid", providerKey))).FirstOrDefault();
            if (user == null)
            {
                user = new IdentityUser(Guid.NewGuid(), providerKey, $"{providerKey}@ilawyer.com",null);
                var result = userManager.CreateAsync(user, "Pass123$").Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
                //添加短信登录角色
                await userManager.AddToRoleAsync(user, "sms");
                result = userManager.AddClaimsAsync(user, new Claim[]{
                    new Claim(JwtClaimTypes.Name,user.Name.IsNullOrEmpty()?user.UserName:user.Name),
                    new Claim("openid", providerKey),
                    //new Claim(JwtClaimTypes.GivenName, user.UserName),
                    //new Claim(JwtClaimTypes.FamilyName, user.NormalizedUserName),
                    new Claim(JwtClaimTypes.Email, user.Email),
                    new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                    new Claim(JwtClaimTypes.WebSite, "https://valishment.cn"),
                }).Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
            }
            var userLogin = new UserLoginInfo(loginProvider, providerKey, "微信登录");
            var loginRes = userManager.AddLoginAsync(user, userLogin).Result;
            if (!loginRes.Succeeded)
            {
                throw new Exception("登陆失败");
            }
        }

        private async Task CaptchaRegisterLogin(ExtensionGrantContext context, string loginProvider, string providerKey)
        {
            var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();

            var user = (await userManager.GetUsersForClaimAsync(new Claim("openid", providerKey))).FirstOrDefault();
            if (user == null)
            {
                user = new IdentityUser(Guid.NewGuid(), providerKey, $"{providerKey}@ilawyer.com", null);
                var result = userManager.CreateAsync(user, "Pass123$").Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
                //添加短信登录角色
                await userManager.AddToRoleAsync(user, "sms");
                result = userManager.AddClaimsAsync(user, new Claim[]{
                    new Claim(JwtClaimTypes.Name,user.Name.IsNullOrEmpty()?user.UserName:user.Name),
                    new Claim("openid", providerKey),
                    //new Claim(JwtClaimTypes.GivenName, user.UserName),
                    //new Claim(JwtClaimTypes.FamilyName, user.NormalizedUserName),
                    new Claim(JwtClaimTypes.Email, user.Email),
                    new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                    new Claim(JwtClaimTypes.WebSite, "https://valishment.cn"),
                }).Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
            }
            var userLogin = new UserLoginInfo(loginProvider, providerKey, "微信登录");
            var loginRes = userManager.AddLoginAsync(user, userLogin).Result;
            if (!loginRes.Succeeded)
            {
                throw new Exception("登陆失败");
            }
        }




        static public string GetRandomString(int len)
        {
            string s = "0123456789abcdefghijklmnopgrstuvwxyz";//在此处也可以加字母A——Z
            string reValue = string.Empty;
            Random rnd = new Random();
            while (reValue.Length < len)
            {
                string s1 = s[rnd.Next(0, s.Length)].ToString();
                if (reValue.IndexOf(s1) == -1) reValue += s1;
            }
            return reValue;
        }

        private async Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray<string> scopes)
        {
            var resources = new List<string>();
            if (!scopes.Any())
            {
                return resources;
            }
            await foreach (var resource in context.HttpContext.RequestServices.GetRequiredService<IOpenIddictScopeManager>().ListResourcesAsync(scopes))
            {
                resources.Add(resource);
            }
            return resources;
        }

        private static IEnumerable<string> GetDestinations(Claim claim)
        {
            // Note: by default, claims are NOT automatically included in the access and identity tokens.
            // To allow OpenIddict to serialize them, you must attach them a destination, that specifies
            // whether they should be included in access tokens, in identity tokens or in both.

            switch (claim.Type)
            {
                case Claims.Name:
                    yield return Destinations.AccessToken;

                    if (HasScope(claim.Subject, Scopes.Profile))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Email:
                    yield return Destinations.AccessToken;

                    if (HasScope(claim.Subject, Scopes.Email))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Role:
                    yield return Destinations.AccessToken;

                    if (HasScope(claim.Subject, Scopes.Roles))
                        yield return Destinations.IdentityToken;

                    yield break;

                // Never include the security stamp in the access and identity tokens, as it's a secret value.
                case "AspNet.Identity.SecurityStamp": yield break;

                default:
                    yield return Destinations.AccessToken;
                    yield break;
            }
        }

        private static bool HasScope(ClaimsIdentity identity, string scope)
        {
            if (identity == null)
            {
                throw new ArgumentNullException("identity");
            }

            if (string.IsNullOrEmpty(scope))
            {
                throw new ArgumentException(OpenIddictResources.GetResourceString("ID0180"), "scope");
            }

            return identity.HasClaim("oi_scp", scope);
        }
    }
}
